package engine

import (
	"context"
	"fmt"

	policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
	"github.com/kyverno/kyverno/api/policies.kyverno.io/v1beta1"
	policiesv1beta1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1beta1"
	"github.com/kyverno/kyverno/pkg/cel/engine"
	ivpolautogen "github.com/kyverno/kyverno/pkg/cel/policies/ivpol/autogen"
	policiesv1alpha1listers "github.com/kyverno/kyverno/pkg/client/listers/policies.kyverno.io/v1alpha1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/client-go/util/workqueue"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type Provider = engine.Provider[Policy]

type ProviderFunc func(context.Context) ([]Policy, error)

func (f ProviderFunc) Fetch(ctx context.Context) ([]Policy, error) {
	return f(ctx)
}

func NewProvider(policies []policiesv1beta1.ImageValidatingPolicyLike, exceptions []*policiesv1alpha1.PolicyException) (ProviderFunc, error) {
	compiled := make([]Policy, 0, len(policies))
	for _, policy := range policies {
		p := policy
		var matchedExceptions []*policiesv1alpha1.PolicyException
		for _, polex := range exceptions {
			for _, ref := range polex.Spec.PolicyRefs {
				if ref.Name == p.GetName() && ref.Kind == p.GetKind() {
					matchedExceptions = append(matchedExceptions, polex)
				}
			}
		}
		actions := sets.New(p.GetSpec().ValidationActions()...)
		compiled = append(compiled, Policy{
			Actions:    actions,
			Policy:     p,
			Exceptions: matchedExceptions,
		})
		if ivp, ok := p.(*v1beta1.ImageValidatingPolicy); ok {
			autogeneratedIvPols, err := ivpolautogen.Autogen(ivp)
			if err != nil {
				return nil, err
			}
			for _, ap := range autogeneratedIvPols {
				compiled = append(compiled, Policy{
					Actions: actions,
					Policy: &v1beta1.ImageValidatingPolicy{
						TypeMeta:   ivp.TypeMeta,
						ObjectMeta: ivp.ObjectMeta,
						Spec:       *ap.Spec,
					},
				})
			}
		}
	}
	provider := func(context.Context) ([]Policy, error) {
		return compiled, nil
	}
	return provider, nil
}

func NewKubeProvider(
	mgr ctrl.Manager,
	polexLister policiesv1alpha1listers.PolicyExceptionLister,
	polexEnabled bool,
) (Provider, error) {
	reconciler := newReconciler(mgr.GetClient(), polexLister, polexEnabled)
	ivpolBuilder := ctrl.NewControllerManagedBy(mgr).
		For(&policiesv1beta1.ImageValidatingPolicy{})

	nivpolBuilder := ctrl.NewControllerManagedBy(mgr).
		For(&policiesv1beta1.NamespacedImageValidatingPolicy{})

	if polexEnabled {
		exceptionHandlerFuncs := &handler.Funcs{
			CreateFunc: func(
				ctx context.Context,
				tce event.TypedCreateEvent[client.Object],
				trli workqueue.TypedRateLimitingInterface[reconcile.Request],
			) {
				polex := tce.Object.(*policiesv1alpha1.PolicyException)
				for _, ref := range polex.Spec.PolicyRefs {
					trli.Add(reconcile.Request{
						NamespacedName: client.ObjectKey{
							Name: ref.Name,
						},
					})
				}
			},
			UpdateFunc: func(
				ctx context.Context,
				tue event.TypedUpdateEvent[client.Object],
				trli workqueue.TypedRateLimitingInterface[reconcile.Request],
			) {
				polex := tue.ObjectNew.(*policiesv1alpha1.PolicyException)
				for _, ref := range polex.Spec.PolicyRefs {
					trli.Add(reconcile.Request{
						NamespacedName: client.ObjectKey{
							Name: ref.Name,
						},
					})
				}
			},
			DeleteFunc: func(
				ctx context.Context,
				tde event.TypedDeleteEvent[client.Object],
				trli workqueue.TypedRateLimitingInterface[reconcile.Request],
			) {
				polex := tde.Object.(*policiesv1alpha1.PolicyException)
				for _, ref := range polex.Spec.PolicyRefs {
					trli.Add(reconcile.Request{
						NamespacedName: client.ObjectKey{
							Name: ref.Name,
						},
					})
				}
			},
		}
		ivpolBuilder = ivpolBuilder.Watches(&policiesv1alpha1.PolicyException{}, exceptionHandlerFuncs)
		nivpolBuilder = nivpolBuilder.Watches(&policiesv1alpha1.PolicyException{}, exceptionHandlerFuncs)
	}

	if err := ivpolBuilder.Complete(reconciler); err != nil {
		return nil, fmt.Errorf("failed to construct imagevalidatingpolicy manager: %w", err)
	}
	if err := nivpolBuilder.Complete(reconciler); err != nil {
		return nil, fmt.Errorf("failed to construct namespacedimagevalidatingpolicy manager: %w", err)
	}
	return reconciler, nil
}
